package com.learn.security.modules.security.sms;

import com.learn.security.common.utils.RedisKeys;
import com.learn.security.common.utils.RedisOperator;
import com.learn.security.modules.security.CustomUserDetailsService;
import com.learn.security.modules.security.LoginUserType;
import com.learn.security.modules.vo.UserDto;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Objects;

/**
 * 短信登陆鉴权 Provider，要求实现 AuthenticationProvider 接口
 * @author jitwxs
 * @since 2019/1/9 13:59
 */
@Slf4j
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    /**
     * 获取用户信息
     */
    private CustomUserDetailsService userDetailsService;

    private RedisOperator redisOperator;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

        UserDto user = (UserDto) authenticationToken.getPrincipal();
        // 校验验证码
        checkSmsCode(user);
        // 获取用户数据
        UserDetails userDetails = userDetailsService.loadUserBySocial(LoginUserType.MOBILE, user);

        // 此时鉴权成功后，应当重新 new 一个拥有鉴权的 authenticationResult 返回
        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());

        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    private void checkSmsCode(UserDto user) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 设置openId
        HttpSession session = request.getSession();
        String openId = (String)session.getAttribute(session.getId());
        log.info("短信登录sessionId:{},openId:{}", session.getId(), openId);
        user.setOpenId(openId);

        String cacheCode = redisOperator.get(RedisKeys.REDIS_SMS_CODE + user.getMobile());
        if(cacheCode == null) {
            log.info("登录失败,未检测到申请验证码");
            throw new BadCredentialsException("登录失败,未检测到申请验证码");
        }
        if(!Objects.equals(cacheCode, user.getSmsCode())) {
            log.info("登录失败,验证码错误");
            throw new BadCredentialsException("登录失败,验证码错误");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        // 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}